Skip to content

Conversation

soifou
Copy link
Collaborator

@soifou soifou commented Aug 12, 2025

Introduce jump_by option for select_next and select_prev commands to jump to the item whose specified property differs from the current one.

Support primitive properties (string, number, boolean) of a completion item, including:

  • client_id
  • client_name
  • deprecated
  • exact
  • kind
  • score
  • score_offset
  • source_id
  • source_name

Honor cycling, set by completion.list.cycle.from_[top|bottom].

Example, jump to the next/previous source_id:

opts.keymap = {
  -- ...
  ['<C-f>'] = {
      function(cmp) return cmp.select_next({ jump_by = 'source_id' }) end,
      'fallback',
  },
  ['<C-b>'] = {
      function(cmp) return cmp.select_prev({ jump_by = 'source_id' }) end,
      'fallback',
  },
},

Closes #1890

@soifou
Copy link
Collaborator Author

soifou commented Aug 12, 2025

@mikavilpas, could you try this and let me know if it works for you?

@mikavilpas
Copy link
Contributor

Works for me!

output.mp4

suggestion: the opts.keymaps should be opts.keymap 🙂 https://cmp.saghen.dev/configuration/keymap.html#example

Introduce `jump_by` option for `select_next` and `select_prev` commands
to jump to the item whose specified property differs from the current
one.

Support primitive properties (`string`, `number`, `boolean`) of a
completion item, including:
- client_id
- client_name
- deprecated
- exact
- kind
- score
- score_offset
- source_id
- source_name

Honor cycling, set by `completion.list.cycle.from_[top|bottom]`.
Falls back to `count` when no match is found.

Example, jump to the next/previous `source_id`:

```lua
opts.keymap = {
  -- ...
  ['<C-f>'] = {
      function(cmp) return cmp.select_next({ jump_by = 'source_id' }) end,
      'fallback',
  },
  ['<C-b>'] = {
      function(cmp) return cmp.select_prev({ jump_by = 'source_id' }) end,
      'fallback',
  },
},
```

Closes #1890
@soifou soifou force-pushed the feat/keymap-jumpby branch from 9f0cc38 to 3705d3a Compare August 12, 2025 19:33
@soifou
Copy link
Collaborator Author

soifou commented Aug 12, 2025

Thanks for the quick feedback and for catching that typo — fixed it!

@Saghen
Copy link
Owner

Saghen commented Aug 12, 2025

This is really cool! Perhaps it makes sense to provide a jump_if option instead (better name needed?), which would look like jump_if = function(curr_item, item) return curr_item.source_Id ~= item.source_id end. It gives a bit more control to the user as iirc I read someone wanted to jump to a specific source or kind. Wdyt?

Falls back to count when no match is found.

Perhaps it makes more sense to return false and use the existing fallback mechanism. So if the user wanted this, they could do:

opts.keymap = {
  -- ...
  ['<C-f>'] = {
      function(cmp) return cmp.select_next({ jump_by = 'source_id' }) end,
      'select_next',
      'fallback',
  },
  ['<C-b>'] = {
      function(cmp) return cmp.select_prev({ jump_by = 'source_id' }) end,
      'select_prev',
      'fallback',
  },
},

P.S. Sorry for the pile up of PRs. I'll be slow to respond for another few weeks, doing a bit of traveling

@soifou
Copy link
Collaborator Author

soifou commented Aug 12, 2025

it gives a bit more control to the user as iirc I read someone wanted to jump to a specific source or kind. Wdyt?

Someone? You mean Mika, right? 😄 Either way works for me. The current setup is simple, easy to use, and has a solid list of jump props, nicely autocompleted. What’s the real benefit of using a function here? Got a concrete example?

Perhaps it makes more sense to return false and use the existing fallback mechanism.

Good point, I’ll address that.

Sorry for the pile up of PRs. I'll be slow to respond for another few weeks, doing a bit of traveling

No worries, take your time and enjoy your travels, you only live once!

@Saghen
Copy link
Owner

Saghen commented Aug 13, 2025

Someone? You mean Mika, right? 😄 Either way works for me. The current setup is simple, easy to use, and has a solid list of jump props, nicely autocompleted. What’s the real benefit of using a function here? Got a concrete example?

Haha it wasn't Mika but I can't find the poster anymore. The use case was skipping past copilot suggestions since they use preselect = false and ghost text (shown when no selection), so selecting it was pointless for them. But they still wanted to select the first entry for documentation, if it wasn't copilot. Either way, what you've written should be enough for the handful of users who want this, so it's fine with me!

Selecting an item was always possible but with the introduction of the
`jump_by` feature, it now *may* not select an item depending on the
context.

Thus, we need to implements a fallback mechanism for the
`select_prev` and `select_next` commands when selection fails.
@soifou
Copy link
Collaborator Author

soifou commented Aug 13, 2025

Thanks for the explanation! I'll see what I can do about it.

To handle the fallback, I had to get rid of vim.schedule for the select_next and select_prev commands since we need to return their results directly. I hope this won't introduce any side effects (I haven't noticed any so far). I couldn't find a clear reason for why it was used here in the first place, but I assume it was to avoid fast context event problems. Since there's no use of vim.fn.* and I don't see any UI involvement in this module, it seems safe to remove.

If you have any other suggestions on how to tackle this, I’d be happy to hear them!

Copy link
Owner

@Saghen Saghen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created a list.can_select function so we can keep the schedule. Iirc, we have to schedule all commands as our keymaps are defined with expr = true for fallback. Removing it also might break some configs so seems good to keep. But I've kept the boolean returns in select_next and select_prev you added, even though they're not used anymore, since they could be useful one day. If you're happy with this, feel free to merge!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

select_next until the next source
3 participants